﻿using Cudafy;
using Cudafy.Host;
using Cudafy.Translator;
using Lights.Types;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Threading;

namespace Lights.Processing
{
    public enum ProcessorType
    {
        CPU, OCL
    }
    /// <summary>
    /// Elabora le immagini
    /// </summary>
    public class PhotoProcessor
    {
        #region Properties
        public ProcessorType Type { get; private set; }
        public byte[] GetBytes
        {
            get
            {
                // se lavoro su dispositivo OCL scarico i dati su ram
                byte[] tmp = new byte[this.width * this.height * 4];
                if (this.gpu != null)
                {
                    this.gpu.CopyFromDevice(this.dst, tmp);
                }
                else
                {
                    tmp = this.dst;
                }
                if (tmp.Count(i => i == 0) == tmp.Length)
                    tmp = src;
                return tmp;
            }
        }
        #endregion

        #region Fields
        private CudafyModule module;
        Dictionary<string, IModule> modules;
        GPGPU gpu;
        Photo photo;
        byte[] src, dst;
        int width, height;
        #endregion

        #region Methods
        public static PhotoProcessor Create()
        {
            // ottengo il miglior dispositivo di elaborazione sul pc in uso
            PhotoProcessor processor = new PhotoProcessor(CudafyHost.GetDeviceCount(eGPUType.OpenCL) > 0 ? ProcessorType.OCL: ProcessorType.CPU);

            // aggiungo i moduli integrati
            processor.modules.Add("Brightness", new Brightness());
            //processor.modules.Add("Contrast", new Contrast());

            // carico tutti i moduli che si trovano nella cartella "Modules"
            try
            {
                string[] files = Directory.GetFiles(Global.ModulesFolder, "*.dll");
                foreach (string file in files)
                {
                    string name = Path.GetFileName(file);
                    string programPath = Path.GetFullPath(file);
                    Assembly asm = Assembly.LoadFile(programPath);
                    processor.modules.Add(name, (IModule)asm.CreateInstance(name));
                }
            }
            catch { }
            // restituisco il Processore
            return processor;
        }

        private PhotoProcessor(ProcessorType type)
        {
            try
            {
                this.modules = new Dictionary<string, IModule>();
                this.Type = type;
                // se sto inizializzando un processore OpenCL
                if (type == ProcessorType.OCL)
                {
                    this.gpu = CudafyHost.GetDevice(eGPUType.OpenCL);
                    this.module = CudafyTranslator.Cudafy(eArchitecture.OpenCL12);
                }
            }
            catch (Exception e){ }
        }

        /// <summary>
        /// Imposta la foto da elaborare
        /// </summary>
        /// <param name="photo"></param>
        public void SetPhoto(Photo photo)
        {
            this.photo = photo;
            BitmapSource img = photo.OriginalImage;

            // estraggo il vettore di byte
            
            
            this.width = img.PixelWidth; this.height = img.PixelHeight;

            int stride = ((this.width * 32 + 31) & ~31) / 8;
            int lenght = stride * this.height;
            this.src = new byte[lenght];
            img.CopyPixels(this.src, stride, 0);

            // se sono un processore OCL inizializzo la scheda
            if (this.gpu != null)
            {
                this.gpu.UnloadModules();
                this.gpu.FreeAll();
                this.src = this.gpu.CopyToDevice(this.src);
                this.dst = this.gpu.Allocate<byte>(lenght);
            }
            else
            {
                this.dst = new byte[this.src.Length];
            }
        }

        /// <summary>
        /// Applica il filtro all'immagine. Se il filtro è già stato applicato in passato aggiorna il valore.
        /// </summary>
        /// <param name="filter">Filtro da applicare</param>
        /// <param name="values">Valori da applicare</param>
        /// <returns></returns>
        public void SetFilter(string filter, params object[] values)
        {
            if (!this.photo.Filters.ContainsKey(filter))
                this.photo.Filters.Add(filter, null);
            
            this.photo.Filters[filter] = values;
            
            if (this.photo.Filters[filter] == null)
                this.photo.Filters.Remove(filter);
            
            if (this.photo.Filters.Count <= 0)
                return;

            // TODO: Applico i filtri ogni volta che ne aggiungo\tolgo\modifico uno???

            string[] filters = this.photo.Filters.Keys.ToArray();
            // applico il primo filtro su src e ottengo dst
            this.modules[filters[0]].Execute(this.gpu, this.src, this.dst, this.width, this.height, this.photo.Filters[filters[0]]);

            //applico il secondo filtro su dst e ottengo dst2
            for (int i = 1; i < filters.Length; i++)
            {
                this.modules[filters[i]].Execute(this.gpu, this.dst, this.dst, this.width, this.height, this.photo.Filters[filters[i]]);
            }
        }

        // ottiene un anteprima dell'immagine elaborata
        public BitmapSource GetPreview()
        {
            // ricostruisco l'immagine e la restituisco
            return BitmapFrame.Create(this.width, this.height, 96.0, 96.0, PixelFormats.Bgra32, null, GetBytes, this.width * 4);
        }

        public Histogram GetHistogram()
        {
            byte[] buffer = GetBytes;
            int length = buffer.Length / 4;
            int[] luminance = new int[256];
            int[] red = new int[256];
            int[] green = new int[256];
            int[] blue = new int[256];

            //Parallel.For(0, length, (i) =>
            //{
            //    int x = i * 4;
            //    Interlocked.Increment(ref luminance[(int)(0.3 * buffer[x + 2] + 0.59 * buffer[x + 1] + 0.11 * buffer[x])]);
            //    Interlocked.Increment(ref red[buffer[x + 2]]);
            //    Interlocked.Increment(ref green[buffer[x + 1]]);
            //    Interlocked.Increment(ref blue[buffer[x]]);
            //});

            return new Histogram(luminance, red, green, blue);
        }
        #endregion
    }
}
